In this section we will add code to the AMReminder example to make it functional. The main window's list will display reminders in a multi-column list, the Add, Edit, and Delete buttons will work as expected. The Edit and Delete buttons will be enabled or disabled depending on whether something is selected in the list. The Open and Save commands will read and write reminder data from/to a file.
We will do this with surprisingly few lines of code that we will enter as code fragments in Commands or Member Get/Set functions. These are the places where you will add most or all of the functional code in your own applications.
AppMaker provides a very flexible and very easy way to define the visual content of a list. For the AMReminder example we will display each reminder with columns for Date, Time, and Message. You first create a Panel to define the content of a row then set the List to use that Panel to display each row. Finally, you specify an array of objects as source data for the list.
If Static Text items are connected to data items, they display the value of the data instead of displaying static text. The Reminder Data Def contains a DateTime item but that is not in a format that a Static Text can display. We'll create "pseudo-data" items to translate DateTime into a DateString and a TimeString.
"Get" code
field, type the following. Your Browser probably will let you copy and paste these lines. Alternatively, you could open the generated DReminder.cp file and copy the code from GetDateString.
LongDateTime longSeconds; static Str255 dateString; LongDateToSeconds (&mDateTime, &longSeconds); LongDateString (&longSeconds, shortDate, dateString, nil); return dateString;
LongDateTime longSeconds; static Str255 timeString; LongDateToSeconds (&mDateTime, &longSeconds); LongTimeString (&longSeconds, false, timeString, nil); return timeString;
The Reminders list box gets its data from the Document's Reminders data item, which is an array of Reminder objects. To display each row, the list box passes a Reminder element to the Reminder Panel. The Panel's window items Get the Date, Time, and Message values from the Reminder object and display them.
AMReminder's main window has three buttons with corresponding Commands: AddReminder, EditReminder, and DeleteReminder. Our next step is to add code to implement these commands.
This command invokes the Add dialog. AppMaker's model for calling a modal dialog is to create a data object, initialize it to appropriate values, then pass the data to the modal dialog. The dialog displays the current data, interacts with the user to change the data, then returned modified data to the caller along with a true or false for OK or Cancel. The calling function, if the dialog returned true, does something with the new data.
To make the command functional you can add a code fragment to initialize the dialog's data from current values of a selected item, or perhaps from user preferences. You can also add a code fragment to do something with the modified data if the dialog returns true. The Command Info dialog lets you enter these two code fragments.
We've found that the easiest way to write these code fragments is to edit the generated code, build and test the application, then copy the few lines of hand-written code into your AppMaker document as code fragments.
The implementations are slightly different for the several languages that AppMaker supports. For C/C++ OS8 the final code is:
For PowerPlant the code is:
You can type the lines marked "hand-written" into the "If true" code fragment in the Command Info dialog. More easily you can open the generated code for AMReminder in the Example folder for either C/C++ OS8 or for PowerPlant and then Copy and Paste the code fragment.
This command also invokes the Edit dialog. It displays data from the selected reminder and if the dialog returns true, it stores the modified data back into that reminder. So this command has two code fragments: it has a "Before" fragment and an "If true" fragment. For C/C++ OS8 the code is:
For PowerPlant the code is:
You can type the lines marked "hand-written" into the Command Info dialog. More easily you can open the generated code for AMReminder in the Example folder for either C/C++ OS8 or for PowerPlant and then Copy and Paste the two code fragments.
The DeleteReminder command removes the selected reminder from the array of reminders, without first displaying a dialog. The command is implemented as a simple code fragment:
Note that this code only modifies the data; it doesn't directly affect the listbox which displays the array of reminders. There is a connection from the data to the listbox so that if an element is removed from the reminders array then the listbox receives a signal that the data has changed. The listbox gets the new data and updates its display.
Whenever possible - and it usually is possible - Commands and other user interface actions don't directly change user interface items. Instead they simply modify data items. The generated code automatically takes care of updating user interface items when data items are changed. You don't have to write that code.
The preceding sections with only twenty or so lines of hand-written code have produced a largely-functional application. Clicking the Add button invokes a dialog. If the user clicks OK, then a new reminder is added to the array, and the listbox displays the new reminder. Clicking Edit or Delete affects the selected reminder as expected.
The next step is to enable or disable the Edit and Delete buttons depending on whether a reminder is selected in the list of reminders. First we'll define a boolean data item. Then we'll set its value depending on whether the list has a selection. Finally, we'll connect the Edit and Delete buttons to the boolean data item.
"Get" code
field, enter the following:
return (mReminderChoice >= 0);
The ReminderChoice data member is zero-based; a value of 0 indicates that the first row of the list is selected. If nothing is selected its value is -1. Its initial value, when nothing is selected, should be -1.
SignalDataChanged (idIsSelected);Whenever the value of ReminderChoice is changed, this signals a change in the value of the IsSelected data item.
We previously connected the Reminders listbox to the RemindersChoice data item so that whenever the list's selection is changed, the RemindersChoice integer will indicate which row is selected. The IsSelected boolean will calculate as true whenever there is a row selected or false if no row is selected. The final step is to connect the Edit and Delete buttons to the IsSelected boolean to enable or disable the buttons.
AppMaker generates code that responds to a change in IsSelected by enabling or disabling the two buttons.
At this point we tested the built application and discovered an unexpected problem with the Clock control. (More about which later.) We programmed around the problem by writing two more pseudo-data items. Like the pseudo-data items we wrote earlier (DateString, TimeString, and IsSelected), these two new items Get data from other data members. Unlike the other items, these two items also Set data into other data items.
So what was the problem with the Clock control? We have two Clock controls. One displays a date, the other displays a time. Both are connected to the DateTime item, which is a LongDateRec - i.e. the data item holds both a date and a time. The Clock control accepts and returns a LongDateRec so we expected both clocks to work correctly. It turned out that both Clocks correctly accepted the DateTime and displayed the current value correctly. But they didn't modify DateTime as planned. We expected each control to modify part of its LongDateRec but to leave the other parts unmodified. Instead, each Clock returned a LongDateRec that contained garbage in the other parts.
The cure was to define pseudo-data which takes only the valid parts of the LongDateRec and stores only those parts into DateTime.
return mDateTime;
mDateTime.ld.year = inValue.ld.year; mDateTime.ld.month = inValue.ld.month; mDateTime.ld.day = inValue.ld.day; SignalDataChanged (idDateTime);
mDateTime.ld.hour = inValue.ld.hour; mDateTime.ld.minute = inValue.ld.minute; SignalDataChanged (idDateTime);
The final step is to connect the two Clock controls to these pseudo-data items instead of to DateTime.
The generated code for the Date control will call GetYearMonthDay to access DateTime and will call SetYearMonthDay to modify only the date portion of DateTime. Similarly, the Time control will call SetHourMinute to modify only the time portion of DateTime.
AppMaker generates code to Read and Write your data so as to implement the Open and Save commands. All you have to do is tell AppMaker which data items are "persistent."
That's all there is to it. AppMaker will generate code to read and write each Persistent data item.
We've added only thirty or so lines of hand-written code and we have an almost fully functional application. Its main window displays a multi-column list of reminders. The Add, Edit, and Delete buttons modify the list as expected. A dialog displays and modifies all of the attributes of a reminder. The Open and Save commands read and write the data.
What's left?
A fairly easy feature to add would be to keep the list of reminders in sorted order. We could add a function member to DocData to sort the array of reminders. The Add and Edit commands would each add a line of code to call the Sort function.
The second and more important feature is to implement the essential purpose of this application, i.e. to set a timer and then to display a reminder at the specified date and time.
In keeping with the idea that user interface and application logic should be separated, we would implement these features in the DataDef code, not in the user interface code. We would add function members to DocData. Alternatively, we might write code in separate files that are called from DocData function members.
This is the general rule. For most applications, you should not have to modify the user interface code. You'll write code fragments for DataDef Members or for Commands. AppMaker will generate code for user interface items and data items to interact, and will generate your code fragments into the appropriate places in the generated code.